#include "us_ctl.h"
#include "uentry.h"
int ratelimit(uint64_t start_us, uint64_t cur_bytes, uint64_t limit_rate)
{
    struct timeval now;
    double elapse;
    uint64_t cur_us;
    double cur_rate;

    gettimeofday(&now, NULL);
    cur_us = TV2US(&now);

    elapse = ((cur_us - start_us) / 1000.0 / 1000.0);
    cur_rate = (cur_bytes * 8) / elapse;
    if (limit_rate > cur_rate)
        return 1;
    else
        return 0;
}
int uentry_send(uentry_t* ue, char* buf, int len)
{
    return sendto(ue->fd, buf, len, 0, (struct sockaddr*)&ue->peer_addr, sizeof(ue->peer_addr));
}

static void _do_download(uctl_conf_t* uconf, uentry_t* ue)
{
    int ret;
    uint64_t* seq = NULL;
    char buffer[2000] = { 0 };
    struct timeval start, now, end;
    double ms_start, ms_now, ms_end;
    uentry_trans_info_t download_info = {0};

    seq = (uint64_t*)buffer;
    gettimeofday(&start, NULL);
    ms_start = TV2MS(&start);
    ms_end = ms_start + uconf->duration;
    while (1) {
        if(!ratelimit(TV2US(&start), download_info.bytes, uconf->rate))
            continue;

        ret = uentry_send(ue, buffer, uconf->blksize);
        if (ret < 0) {
            log_syserr("send failed.");
            return;
        }
        download_info.bytes += ret;
        download_info.packets++;
        (*seq)++;
        gettimeofday(&now, NULL);
        ms_now = TV2MS(&now);
        if (ms_now > ms_end) {
            break;
        }
    }
    log_trace("last seq:%llu", *seq);
}

void* uentry_run(void* arg)
{
    uentry_t* ue = (uentry_t*)arg;
    int ret, timeout_count = 0;
    char rcv_buf[UCTL_BUFFER_MAX] = { 0 }, snd_buf[UCTL_BUFFER_MAX] = { 0 };
    fd_set rdfds, wrfds;
    struct timeval tmo;
    uctl_conf_t uconf, resp_uconf;
    uentry_trans_info_t upload_info = { 0 };

    bzero(&uconf, sizeof(uconf));
    bzero(&resp_uconf, sizeof(resp_uconf));
    while (1) {
        FD_ZERO(&rdfds);
        FD_ZERO(&wrfds);
        FD_SET(ue->fd, &rdfds);
        tmo.tv_sec = 1;
        tmo.tv_usec = 0;
        ret = select(ue->fd + 1, &rdfds, &wrfds, NULL, &tmo);
        if (ret == 0) {
            if ((timeout_count++) > 10) {
                break;
            }
        } else if (FD_ISSET(ue->fd, &rdfds)) {
            timeout_count = 0;
            ue->peer_addr_len = sizeof(ue->peer_addr);
            ret = recvfrom(ue->fd, rcv_buf, sizeof(rcv_buf), 0, (struct sockaddr*)&ue->peer_addr, &ue->peer_addr_len);
            if (ret > 0) {
                if (rcv_buf[0] == 0) {
                    upload_info.bytes += ret;
                    upload_info.packets++;
                    continue;
                }
                uctl_conf_str2struct(&uconf, rcv_buf, ret);
                if (!strcmp(uconf.cmd, UCTL_CONF_CMD_DOWNLOAD)) {
                    log_trace("do download");
                    _do_download(&uconf, ue);
                } else if (!strcmp(uconf.cmd, UCTL_CONF_CMD_UPLOAD)) {
                    log_trace("do upload");
                    strcpy(resp_uconf.cmd, UCTL_CONF_CMD_ACK);
                    uctl_conf_struct2str(&resp_uconf, snd_buf, sizeof(snd_buf));
                    uentry_send(ue, snd_buf, strlen(snd_buf));
                } else if (!strcmp(uconf.cmd, UCTL_CONF_CMD_UPLOAD_FINISH)) {
                    strcpy(resp_uconf.cmd, UCTL_CONF_CMD_UPLOAD_REPORT);
                    resp_uconf.bytes = upload_info.bytes;
                    resp_uconf.packets = upload_info.packets;
                    uctl_conf_struct2str(&resp_uconf, snd_buf, sizeof(snd_buf));
                    uentry_send(ue, snd_buf, strlen(snd_buf));
                } else if (!strcmp(uconf.cmd, UCTL_CONF_CMD_CLOSE)) {
                    uentry_send(ue, rcv_buf, ret); //close, direct return;
                    break;
                }
            }
        }
    }
    uentry_free(ue);
    log_trace("uentry_run exit");
    return NULL;
}

int uentry_bind(uentry_t* ue)
{
    int retry_max = 1000;

    ue->fd = socket_dgram_new();
    ue->port = port_random(0);
    sockaddr_set(&ue->local_addr, AF_INET, htons(ue->port), "0.0.0.0");

    while (1) {
        if (bind(ue->fd, (struct sockaddr*)&ue->local_addr, sizeof(ue->local_addr)) == 0) {
            return 1;
        }
        ue->port++;
        sockaddr_set(&ue->local_addr, AF_INET, htons(ue->port), "0.0.0.0");
        if ((--retry_max) == 0)
            break;
    }
    return 0;
}

uentry_t* uentry_new()
{
    uentry_t* ue = calloc(1, sizeof(uentry_t));
    if (!ue) {
        log_syserr("Calloc uentry failed.");
        return NULL;
    }
    return ue;
}

void uentry_free(uentry_t* ue)
{
    if (!ue)
        return;
    if (ue->fd)
        close(ue->fd);
    free(ue);
}